package cn.echisan.springbootjwtdemo.config;

import cn.echisan.springbootjwtdemo.exception.DepAuthenticationFailureHandler;
import cn.echisan.springbootjwtdemo.exception.DepAuthenticationSuccessHandler;
import cn.echisan.springbootjwtdemo.exception.JWTAccessDeniedHandler;
import cn.echisan.springbootjwtdemo.exception.JWTAuthenticationEntryPoint;
import cn.echisan.springbootjwtdemo.filter.JWTAuthenticationFilter;
import cn.echisan.springbootjwtdemo.filter.JWTAuthorizationFilter;
import cn.echisan.springbootjwtdemo.voter.AbilityVote;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * Created by echisan on 2018/6/23
 */
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(100)
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.debug(true);
    }

//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        //不需要默认配置，所以覆盖父类方法
//        //如果注销，则需要自己提供authenticationManager
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //处理jwt开头的请求，注意@Order(100)，这是前面的一个
        http.requestMatcher(new AntPathRequestMatcher("/jwt/**"))
                .addFilter(jwtAuthenticationFilter("/jwt/auth/login"))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .cors(cors -> {
                    cors.configurationSource(corsConfigurationSource());
                })  //支持跨域
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .antMatchers("/jwt/admin/**").hasRole("ADMIN")
                                .antMatchers("/jwt/tasks/**").authenticated()
                                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()//允许跨域请求
                                .accessDecisionManager(accessDecisionManager())
                )
                // 基于token，所以不需要session
                .sessionManagement(sessionManagement ->
                        sessionManagement
                                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .exceptionHandling(exceptionHandlingConfigurer ->
                        exceptionHandlingConfigurer
                                .authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                                .accessDeniedHandler(new JWTAccessDeniedHandler()) //添加无权限时的处理
                )
                .headers(headersConfigurer ->
                        headersConfigurer.cacheControl().disable()
                )
                .logout(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .rememberMe(AbstractHttpConfigurer::disable)
        ;
    }

    public AccessDecisionManager accessDecisionManager() {
        return new UnanimousBased(Arrays.asList(new WebExpressionVoter(), new AbilityVote()));
    }

    public JWTAuthenticationFilter jwtAuthenticationFilter(String filterProcessesUrl) throws Exception {
        JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager(), objectMapper, filterProcessesUrl);
        jwtAuthenticationFilter.setAuthenticationFailureHandler(new DepAuthenticationFailureHandler());
        jwtAuthenticationFilter.setAuthenticationSuccessHandler(new DepAuthenticationSuccessHandler(objectMapper));
        return jwtAuthenticationFilter;
    }

    /**
     * cors允许，需要将此注入容器
     * 网络传说
     *
     * @return
     */
//    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

    /**
     * cors允许，需要将此注入容器
     * 网络传说
     *
     * @return
     */
    //    @Bean
    protected CorsConfigurationSource corsConfigurationSourceA() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "HEAD", "OPTION"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.addExposedHeader("Authorization");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
